package rs.actor

import akka.actor.typed.receptionist.Receptionist
import akka.actor.typed.{ActorRef, ActorSystem, Behavior}
import akka.actor.typed.scaladsl.{ActorContext, Behaviors}
import akka.stream.IOResult
import akka.stream.scaladsl.{FileIO, Source}
import akka.util.ByteString
import com.typesafe.scalalogging.Logger

import scala.io.BufferedSource
import java.io.{File, FileInputStream, FileNotFoundException}
import java.nio.file.Paths
import scala.concurrent.duration.{DurationInt, FiniteDuration, span}
import scala.concurrent.{ExecutionContext, Future}


sealed trait JoyCommand extends BaseCommand
case class JoyBtnEvent(btnNum: Int, isDown: Boolean) extends JoyCommand
case class JoyAxisEvent(axisNum: Int, axisValue: Int) extends JoyCommand

sealed trait JoyControl
case class JoyStart() extends JoyControl
case class JoyStop() extends JoyControl

object JoySticker {

  import rs.controllerKey
  var controllerList = Set[ActorRef[BaseCommand]]()

  def apply() = {
    Behaviors.setup[JoyControl] { ctx =>

      val blist = ctx.spawn(Behaviors.receiveMessage{ (list: Receptionist.Listing) =>
        val si: Set[ActorRef[BaseCommand]] = list.serviceInstances(controllerKey)
        this.controllerList = si
        println(s"update actor A list: ${this.controllerList}")
        Behaviors.same
      }, "ctlList")
      ctx.system.receptionist ! Receptionist.Subscribe(controllerKey, blist.ref)

      new JoySticker(ctx).init()
    }
  }
}

class JoySticker(ctx: ActorContext[JoyControl]) {

  import JoySticker.controllerList

  val log = Logger(getClass)

  val inputFileName = "/dev/input/js0"

  extension (b: Byte) {
    def unsign(bit: Int = 0): Long = (b & 0xff).toLong << bit
  }

  def init(): Behavior[JoyControl] = Behaviors.receiveMessage { (msg: JoyControl) =>
    val f:File = File(inputFileName)
    if (f.exists() && f.canRead()) {
      ctx.self.tell(msg)
      start(new FileInputStream(f))
    } else {
      ctx.scheduleOnce(1 second span, ctx.self, msg)
      Behaviors.same
    }
  }

  def start(os: FileInputStream):Behavior[JoyControl] = Behaviors.receiveMessage { (msg: JoyControl) =>
    msg match {
      case x: JoyStart => {
        /**
         * 如果使用akka stream, 底层nio在读取文件前会执行seek操作，但是 js0 是不支持seek的
         */
        println("start joy")
        given system: ActorSystem[Nothing] = ctx.system
        given ec : ExecutionContext = ctx.executionContext

        Future {
          while(true) {
            val ev = os.readNBytes(8)
            val timeStamp = ev(0).unsign() + ev(1).unsign(8) + ev(2).unsign(16) + ev(3).unsign(24)
            val value = ev(4).toLong + ev(5).toLong << 8
            val type1 = ev(6).unsign()
            val num = ev(7).unsign()

            type1 match {
              case 1 => {
                if (value==256)
                  controllerList.foreach(_.tell(JoyBtnEvent(num.toInt, true)))
                else
                  controllerList.foreach(_.tell(JoyBtnEvent(num.toInt, false)))
              }
              case 2 => {
                controllerList.foreach(_.tell(JoyAxisEvent(num.toInt, value.toInt)))
              }
              case _ => printf(s"time: %d.%3d  $value $type1 $num \n", timeStamp/1000, timeStamp%1000)
            }
          }
          println("finish joystick")
        }
        Behaviors.same
      }
      case x: JoyStop => {
        ctx.self.tell(JoyStart())
        init()
      }
    }
  }

  def stop(os: FileInputStream): Behavior[JoyControl] = Behaviors.receiveMessage { joyHandler =>
    os.close()
    Behaviors.ignore
  }
}

object JoyLinkDetector {

  def apply(joysticker: ActorRef[JoyControl]): Unit =  {
    Behaviors.setup[String] { (ctx: ActorContext[String]) =>
      Behaviors.withTimers { timer =>
        timer.startTimerWithFixedDelay("check link", 1 second span)
        new JoyLinkDetector(ctx, joysticker).start()
      }
    }
  }
}

class JoyLinkDetector(ctx:ActorContext[String], joySticker: ActorRef[JoyControl]){

  def start():Behavior[String] = Behaviors.receiveMessage { msg =>
    val s: BufferedSource = scala.io.Source.fromFile("/proc/bus/input/devices")
    val isLinked = s.getLines().filter(_.contains("Microsoft X-Box 360 pad")).size > 0
    if (!isLinked) joySticker.tell(JoyStop()) //else actorRef.tell(JoyStart())
    Behaviors.same
  }

}
